package channel

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"math"
	"os"
	"runtime"
	"strings"
)

//Define channel interface
type Channel interface {
	//Check channel is opened
	IsOpen() bool
	//Close channel
	Close()
	//Get the last read data length of the channel
	Length() int
	//Get the total length of channel reads
	TotalLength() int64
	//Get the number of channel reads
	Count() int64
	//Get the offset of the file in the channel (file pointer offset)
	Offset() int64
	//Set the offset of the file in the channel (file pointer offset)
	SetOffset(offset int64)
	//Get the maximum offset of the file in the channel (file length: byte)
	MaxOffset() int64
	//Read the data in the channel
	Read() *[]byte
	//Read the end character data in the channel
	ReadRune(end rune, includeRune bool) *[]byte
	//Read data in channel by line
	ReadLine() *[]byte
	//Get all data of files in the channel
	ReadEnd() *[]byte
}

//Define buffered channel struct
type BufferedChannel struct {
	file        string   //Source file
	filePtr     *os.File //File pointer
	buffer      *[]byte  //Buffer array
	bufferSize  int      //Buffered size
	offset      int64    //File pointer current position
	maxOffset   int64    //File pointer max position (File total length)
	count       int64    //Read count
	length      int      //Last read length
	totalLength int64    //Total read length
}

func NewBufferedChannel(file string, bufferSize int) *BufferedChannel {
	if bufferSize <= 0 {
		log.Fatalf("A buffer size must greater than 0")
		return nil
	}
	filePtr, err := os.Open(file)
	if err != nil {
		log.Fatalf("%v", err)
		return nil
	}
	fileInfo, err := filePtr.Stat()
	if err != nil {
		log.Fatalf("%v", err)
		return nil
	}
	maxOffset := fileInfo.Size()
	bp := make([]byte, bufferSize)
	return &BufferedChannel{file: file,
		filePtr:    filePtr,
		bufferSize: bufferSize,
		buffer:     &bp,
		count:      0,
		offset:     0,
		maxOffset:  maxOffset}
}

func (channel *BufferedChannel) IsOpen() bool {
	return channel.filePtr != nil
}

func (channel *BufferedChannel) File() *os.File {
	if !channel.IsOpen() {
		fmt.Errorf("FileChannel is closed")
		return nil
	}
	return channel.filePtr
}

func (channel *BufferedChannel) Close() {
	if channel.filePtr == nil {
		return
	}
	channel.filePtr.Close()
	channel.filePtr = nil
}

func (channel *BufferedChannel) Length() int {
	return channel.length
}

func (channel *BufferedChannel) TotalLength() int64 {
	return channel.totalLength
}

func (channel *BufferedChannel) Count() int64 {
	return channel.count
}

func (channel *BufferedChannel) Offset() int64 {
	return channel.offset
}

func (channel *BufferedChannel) SetOffset(offset int64) {
	if offset >= channel.maxOffset {
		channel.offset = channel.maxOffset
	} else {
		channel.offset = offset
	}
	channel.filePtr.Seek(channel.offset, io.SeekStart)
}

func (channel *BufferedChannel) MaxOffset() int64 {
	return channel.maxOffset
}

//read by buffer
func (channel *BufferedChannel) Read() *[]byte {
	return channel.read()
}

func (channel *BufferedChannel) hasRead() bool {
	return channel.Offset() < channel.MaxOffset()
}

func (channel *BufferedChannel) read() *[]byte {
	if !channel.hasRead() {
		return nil
	}
	length := int(math.Min(float64(channel.bufferSize), float64(channel.maxOffset)))
	if length <= 0 {
		return nil
	}
	var buffer []byte
	tmpBuffer := make([]byte, length)
	readLen, err := channel.filePtr.Read(tmpBuffer)
	if err == nil && readLen > 0 {
		channel.length = readLen
		channel.totalLength += int64(readLen)
		channel.count++
		channel.SetOffset(channel.offset + int64(readLen))
		buffer = tmpBuffer[0:readLen]
	}
	return &buffer
}

func (channel *BufferedChannel) ReadLine() *[]byte {
	var (
		WINDOWS = "windows"
		LINUX   = "linux"
		UNIX    = "unix"
		MACOS   = "macos"
	)

	switch goos := strings.ToLower(runtime.GOOS); goos {
	default:
		return nil
	case WINDOWS, LINUX, UNIX:
		buffer := channel.ReadRune('\n', false)
		if buffer == nil {
			return buffer
		}
		if goos == WINDOWS {
			//remove \r
			*buffer = bytes.TrimSuffix(*buffer, []byte{byte('\r')})
			*buffer = bytes.TrimPrefix(*buffer, []byte{byte('\r')})
		}
		return buffer
	case MACOS:
		return channel.ReadRune('\r', false)
	}
}

//read by rune
func (channel *BufferedChannel) ReadRune(end rune, includeRune bool) *[]byte {
	buffer := make([]byte, 0)
	for {
		tmpBufferPtr := channel.Read()
		if tmpBufferPtr == nil {
			break
		}
		tmpBuffer := *tmpBufferPtr
		runeIndex := -1
		for index, char := range tmpBuffer {
			if char == byte(end) {
				runeIndex = index
				break
			}
		}
		if runeIndex > -1 {
			runeOffset := runeIndex
			if includeRune {
				runeOffset++
			}
			buffer = append(buffer, tmpBuffer[:runeOffset]...)
			length := len(*tmpBufferPtr)
			//fix offset
			if runeIndex < length {
				channel.SetOffset(channel.offset - int64(length-runeIndex-1))
			}
			break
		}
		buffer = append(buffer, tmpBuffer...)
	}
	if buffer == nil || len(buffer) <= 0 {
		return nil
	}
	return &buffer
}

//read by end
func (channel *BufferedChannel) ReadEnd() *[]byte {
	var buffer []byte
	for {
		tmpBufferPtr := channel.read()
		if tmpBufferPtr == nil {
			break
		}
		tmpBuffer := *tmpBufferPtr
		buffer = append(buffer, tmpBuffer...)
	}
	return &buffer
}
